/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <cstdio>
#include <iostream>
#include <fstream>
#include <cassert>
#include "Bruinbase.h"
#include "BTreeIndex.h"
#include "SqlEngine.h"

using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);


RC SqlEngine::run(FILE* commandline)
{
  fprintf(stdout, "Bruinbase> ");

  // set the command line input and start parsing user input
  sqlin = commandline;
  sqlparse();  // sqlparse() is defined in SqlParser.tab.c generated from
               // SqlParser.y by bison (bison is GNU equivalent of yacc)

  return 0;
}

#define MIN_INT			(1 << 31)

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond)
{
	RecordFile rf;
	RecordId rid;
	BTreeIndex idx;
	IndexCursor cursor;

	RC       rc;
	bool     have_index;
	bool     use_index;
	bool     have_tuple;
	int      count;
	int      diff;
	int      key;
	string   value;

	have_index = false;
	use_index = false;
	// Open the record file
	if ((rc = rf.open(table + ".tbl", 'r')) < 0){
		fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
		return rc;
	}
	rid.pid = rid.sid = 0;

	// seek to the start of the index by searching for the smallest possible integer
	int start = MIN_INT;
	// Open the index file
	if ((rc = idx.open(table + ".idx", 'r')) == 0){
		bool have_start = false;
		bool have_end = false;
		bool have_value_cond = false;

		// Check whether the conditions either contain an equal statement, 
		// or contains a range
		have_index = true;
		int n;
		for (unsigned i = 0; i < cond.size(); i++){
			// is key
			if (cond[i].attr == 1){
				switch (cond[i].comp){
					// equal statement
					case SelCond::EQ:
						use_index = true;
						start = atoi(cond[i].value);
						break;
					// greater then/greater than or equal statement
					case SelCond::GE:
					case SelCond::GT:
						have_start = true;
						n = atoi(cond[i].value);
						if (n > start)
							start = n;
						break;
					// less than/less than or equal statement
					case SelCond::LE:
					case SelCond::LT:
						have_end = true;
					default:
						break;
				}
			} else if (cond[i].attr == 2){
				have_value_cond = true;
			}
		}
		// Use the index if either an 1) equal statement or 2) a range was specified or
		// 3) key or count is specified and no value conditions exist
		use_index = (use_index || (have_start || have_end)) ||
			((attr == 1 || attr == 4) && !have_value_cond);
	}
	count = 0;
	// Seek to the beginning
	if (use_index)
		idx.locate(start, cursor);
	while (true){
		have_tuple = false;
		// Move the index cursor forward
		if (use_index){
			if ((rc = idx.readForward(cursor, key, rid)) < 0)
				break;
		} else {
			// Just read the tuple from the record file
			if (rid >= rf.endRid())
				break;
			if ((rc = rf.read(rid, key, value)) < 0){
				fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
				goto end;
			}
			have_tuple = true;
		}
		for (unsigned i = 0; i < cond.size(); i++){
      	// compute the difference between the tuple value and the condition value
			switch (cond[i].attr) {
				case 1:
					diff = key - atoi(cond[i].value);
					break;
				case 2:
					if (!have_tuple){
						if ((rc = rf.read(rid, key, value)) < 0){
      						fprintf(stderr, "Error: while reading a tuple from table %s\n",
								table.c_str());
							goto end;
						}
						have_tuple = true;
					};
					diff = strcmp(value.c_str(), cond[i].value);
					break;
			}
			// We can use the index to answer this query
			if (use_index && cond[i].attr == 1){
				switch (cond[i].comp){
					case SelCond::GE:
						if (diff < 0) goto next_tuple;
						break;
					case SelCond::GT:
						if (diff <= 0) goto next_tuple;
						break;
					case SelCond::NE:
						if (diff == 0) goto next_tuple;
						break;
					// If this key is greater exit the loop since all keys
					// will be greater
					case SelCond::EQ:
						if (diff > 0)
							goto exit_loop;
						else if (diff != 0)
							goto next_tuple;
						break;
					// Likewise
					case SelCond::LE:
						if (diff > 0) goto exit_loop;
						break;
					case SelCond::LT:
						if (diff >= 0) goto exit_loop;
						break;
				}
			// else use the default comparison
			} else {
				switch (cond[i].comp){
					case SelCond::GE:
						if (diff < 0) goto next_tuple;
						break;
					case SelCond::GT:
						if (diff <= 0) goto next_tuple;
						break;
					case SelCond::EQ:
						if (diff != 0) goto next_tuple;
						break;
					case SelCond::NE:
						if (diff == 0) goto next_tuple;
						break;
					case SelCond::LE:
						if (diff > 0) goto next_tuple;
						break;
					case SelCond::LT:
						if (diff >= 0) goto next_tuple;
						break;
				}
			}
		}
		// Increase count
		count++;
		// Print the tuple
		switch (attr){
			case 1: fprintf(stdout, "%d\n", key); break;
			case 2:
				if (!have_tuple)
					if ((rc = rf.read(rid, key, value)) < 0){
						fprintf(stderr, "Error: while reading a tuple from table %s\n",
							table.c_str());
						goto end;
					}
				fprintf(stdout, "%s\n", value.c_str()); break;
			case 3:
				if (!have_tuple)
					if ((rc = rf.read(rid, key, value)) < 0){
						fprintf(stderr, "Error: while reading a tuple from table %s\n",
							table.c_str());
						goto end;
					}
				fprintf(stdout, "%d '%s'\n", key, value.c_str()); break;
		}
	next_tuple:
		if (!use_index)
			++rid;
		continue;
	exit_loop:
		break;
	}
	rc = 0;
	if (attr == 4)
    	fprintf(stdout, "%d\n", count);
end:
	// Close the index when we're done
	if (have_index)
		idx.close();
	rf.close();
	return rc;
}

RC SqlEngine::load(const string& table, const string& loadfile, bool index)
{
  RC rc;
  RecordFile rf;
  FILE *lf;
  BTreeIndex idx;
  char buffer[512];

  // Open load file
  if ((lf = fopen(loadfile.c_str(), "r")) == NULL)
    return -1;
  // Open the table file
  if ((rc = rf.open(table + ".tbl", 'w')) < 0){
    fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
	fclose(lf);
    return rc;
  }
  // Open the index file
  if (index){
	if ((rc = idx.open(table + ".idx", 'w')) < 0){
		fprintf(stderr, "Error: cannot open index %s\n", table.c_str());
	}
  }
 // Read each line
  while (fgets(buffer, 512, lf) != NULL){
	string line(buffer);
	int key;
	string value;
	RecordId rid;
	// Parse each line and append
	if ((rc = parseLoadLine(line, key, value)) < 0)
		goto end;
	if ((rc = rf.append(key, value, rid)) < 0)
		goto end;
	// Insert into index if WITH INDEX was specified
	if (index)
		idx.insert(key, rid);
  }
  rc = 0;
end:
  if (index){
	idx.close();
  }
  fclose(lf);
  rf.close();
  return rc;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value)
{
    const char *s;
    char        c;
    string::size_type loc;
    
    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') { c = *++s; }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) { return RC_INVALID_FILE_FORMAT; }

    // ignore white spaces
    do { c = *++s; } while (c == ' ' || c == '\t');
    
    // if there is nothing left, set the value to empty string
    if (c == 0) { 
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) { value.erase(loc); }

    return 0;
}
